package org.liaohailong.library.widget.ptr;

import android.content.Context;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.FrameLayout;

import androidx.annotation.IntDef;
import androidx.annotation.NonNull;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * Author: Victor
 * Date: 2019/5/9 10:22
 * Description: 下拉刷新控件
 * 使用方式：
 * 1，必须在xml中使用
 * 2，
 * <PullToRefreshLayout>
 *     <view implement IPullToRefreshHeader /> -> Header
 *     <View /> -> Content
 * </PullToRefreshLayout>
 * PullToRefreshLayout 中有且仅有两个view：
 *  第一个是实现了IPullToRefreshHeader的View
 *  第二个是内容布局，高度为PullToRefreshLayout的高度
 */
public class PullToRefreshLayout extends FrameLayout {
    private static final String TAG = "PullToRefreshLayout";

    private void log(String format, Object... args) {
        String msg = String.format(format, args);
        Log.i(TAG, msg);
    }

    /**
     * 下拉刷新的头部View
     */
    private View headerView;

    /**
     * 内容View
     */
    private View contentView;

    /**
     * 滑动处理者
     */
    private PullToRefreshHandler handler;

    /**
     * 最小滑动距离
     */
    private int touchSlop;

    /**
     * 滑动至触发刷新的距离
     */
    private int refreshDistance = 0;

    @Status
    private int status = Status.RESET;

    private OnPullToRefreshCallback onPullToRefreshCallback;

    public PullToRefreshLayout(Context context) {
        this(context, null);
    }

    public PullToRefreshLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PullToRefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        handler = new PullToRefreshHandler(this);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        if (getChildCount() != 2) {
            throw new IllegalArgumentException("you should add header view and content view!");
        }
        headerView = getChildAt(0);
        boolean isHeader = headerView instanceof IPullToRefreshHeader;
        if (!isHeader) {
            throw new IllegalArgumentException("header view should be implemented by IPullToRefreshHeader!");
        }
        contentView = getChildAt(1);
    }

    private IPullToRefreshHeader getPullToRefreshHeader() {
        return (IPullToRefreshHeader) headerView;
    }

    public void setOnPullToRefreshCallback(OnPullToRefreshCallback onPullToRefreshCallback) {
        this.onPullToRefreshCallback = onPullToRefreshCallback;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //先将HeaderView移出整个画面
        if (headerView == null) return;
        int headerHeight = headerView.getMeasuredHeight();
        MarginLayoutParams layoutParams = (MarginLayoutParams) headerView.getLayoutParams();
        layoutParams.topMargin = -headerHeight;
        headerView.setLayoutParams(layoutParams);
        //最大滑动距离，让内容布局+头部视图都看不见就够了
        int maxPullDownDistance = h + headerHeight;
        //初始化设置PullToRefreshHandler
        handler.setMaxPullDownDistance(maxPullDownDistance);
        //HeaderView的高度作触发下拉刷新的距离
        refreshDistance = headerHeight;
        handler.setRefreshDistance(refreshDistance);
    }

    /**
     * 记录上次，最后一指按下的位置
     * 记录：
     * 1，onInterceptTouchEvent 拦截成功之后，后续多指 按下/松开 的事件需要在 onTouchEvent 中监听
     * 2，多指事件监听，需要使用 event.getActionMasked() 来判断
     * 3，lastTouchPoint 的修改，仅在松开最后一指时，将倒数第二指的信息更新到此
     * 4，event.getActionIndex() 获取本次操作 按下/松开 的 pointerIndex
     * 5，onTouchEvent 中 ACTION_MOVE 的操作都使用获取最后一指的位置来计算
     * 6，执行顺序：ACTION_POINTER_DOWN -> ACTION_POINTER_UP -> ACTION_MOVE
     */
    private final PointF lastTouchPoint = new PointF();

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean shouldInterceptTouchEvent = false;
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastTouchPoint.set(ev.getX(), ev.getY());
                handler.abortScroll();
                shouldInterceptTouchEvent = false;
                break;
            case MotionEvent.ACTION_MOVE:
                float x = ev.getX();
                float y = ev.getY();

                float xOffset = x - lastTouchPoint.x;
                float yOffset = y - lastTouchPoint.y;
                int xOffsetAbs = Math.round(Math.abs(xOffset));
                int yOffsetAbs = Math.round(Math.abs(yOffset));

                boolean isScrollY = yOffsetAbs > touchSlop;
                boolean isPullVertical = yOffsetAbs > xOffsetAbs;
                boolean isPullDown = yOffset > 0;

                if (status == Status.REFRESHING) {
                    shouldInterceptTouchEvent = true;
                } else if (isScrollY && isPullVertical && isPullDown && !canChildScrollDown(contentView)) {
                    shouldInterceptTouchEvent = true;
                }

                lastTouchPoint.set(ev.getX(0), ev.getY(0));
                break;
            default:
                shouldInterceptTouchEvent = false;
                break;
        }
        if (shouldInterceptTouchEvent) {
            return true;
        } else {
            return super.onInterceptTouchEvent(ev);
        }
    }

    /**
     * @param event 将 event 中最后一指的位置信息记录一下
     */
    private void updateLastPointerEvent(MotionEvent event) {
        if (event == null) return;
        int pointerCount = event.getPointerCount();
        if (pointerCount > 0) {
            // ACTION_POINTER_UP回调时，event里面依然保留上次手指的信息，此时应该取最后一个手指的信息
            int lastPointerId = pointerCount - 1;

            float lastX = event.getX(lastPointerId);
            float lastY = event.getY(lastPointerId);
            lastTouchPoint.set(lastX, lastY);
//            log("lastTouchPoint: %s", lastTouchPoint.toString());
//            log("lastTouchPoint: pointer count: %s, lastPointerId: %s", pointerCount, lastPointerId);
        }
    }

    /**
     * @param event 触点事件
     * @return 获取最后一指，y的位置
     */
    private float getLastPointerEventY(MotionEvent event) {
        if (event == null) return 0;
        int pointerCount = event.getPointerCount();
        if (pointerCount > 0) {
            int lastPointerId = pointerCount - 1;
            return event.getY(lastPointerId);
        }
        return 0;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_POINTER_DOWN: {
                log("onTouchEvent MotionEvent.ACTION_POINTER_DOWN");
                updateLastPointerEvent(event);
                int actionIndex = event.getActionIndex();
                log("actionIndex: %s", actionIndex);
            }
            break;
            case MotionEvent.ACTION_POINTER_UP: {
                log("onTouchEvent MotionEvent.ACTION_POINTER_UP");
                int actionIndex = event.getActionIndex();
                log("actionIndex: %s", actionIndex);
                int pointerCount = event.getPointerCount();
                int lastPointerIndex = pointerCount - 1;
                if (actionIndex == lastPointerIndex) {
                    // 如果松的是最后一根手指，就更新触点了
                    int lastPointerId = pointerCount - 2;

                    float lastX = event.getX(lastPointerId);
                    float lastY = event.getY(lastPointerId);
                    lastTouchPoint.set(lastX, lastY);
                } else {
                    // 如果松开的不是最后一根手指，可以继续保持
                }
            }

            break;
            case MotionEvent.ACTION_MOVE:
                // 获取最后一指，作为操作的计算点
                float y = getLastPointerEventY(event);
                float yOffset = y - lastTouchPoint.y;
                int yOffsetAbs = Math.round(Math.abs(yOffset));

                if (status == Status.RESET) {
                    status = Status.PULLING;
                }

//                log("yOffsetAbs: %s", yOffsetAbs);
                boolean isPullDown = yOffset > 0;
                handler.movePos(yOffsetAbs, isPullDown);
                getPullToRefreshHeader().onPulling(handler);

                // 保存上次最后一指按下的位置，防止距离累计
                updateLastPointerEvent(event);
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                int scrollY = Math.abs(getScrollY());
                if (status == Status.PULLING) {
                    //如果滑动距离大于触发刷新的距离，就开始进入刷新状态
                    if (scrollY > refreshDistance) {
                        status = Status.REFRESH_BEGIN;
                        getPullToRefreshHeader().onBeforeRefresh();
                        handler.showHeader();
                        status = Status.REFRESHING;
                        getPullToRefreshHeader().onRefreshing();
                        if (onPullToRefreshCallback != null) {
                            onPullToRefreshCallback.onRefreshBegin();
                        }
                    } else {
                        status = Status.RESET;
                        handler.showContent();
                        getPullToRefreshHeader().onReset();
                    }
                } else if (status == Status.REFRESHING) {
                    if (scrollY > refreshDistance) {
                        /*
                         * 用户在刷新时移动了UI，但任然还是刷新范围内，
                         * 只做动画和状态处理，不回调外部接口，因为本次刷新任务还未结束
                         * */
                        status = Status.REFRESH_BEGIN;
                        handler.showHeader();
                        status = Status.REFRESHING;
                    } else {
                        status = Status.REFRESH_CANCELLED;
                        getPullToRefreshHeader().onRefreshCancelled();
                        if (onPullToRefreshCallback != null) {
                            onPullToRefreshCallback.onRefreshCancel();
                        }
                        handler.showContent();
                        status = Status.REFRESH_END;
                        getPullToRefreshHeader().onAfterRefresh();
                        status = Status.RESET;
                        getPullToRefreshHeader().onReset();
                    }
                }
                break;
            default:
                break;
        }
        return true;
    }

    private boolean canChildScrollDown(@NonNull View childView) {
        if (childView instanceof ViewGroup) {
            ViewGroup viewGroup = (ViewGroup) childView;
            for (int i = 0; i < viewGroup.getChildCount(); i++) {
                View view = viewGroup.getChildAt(i);
                boolean canScrollDown = canChildScrollDown(view);
                if (canScrollDown) {
                    return true;
                }
            }
        }
        return childView.canScrollVertically(-1);
    }


    @Override
    public void computeScroll() {
        if (handler.computeScroll()) {
            if (status == Status.REFRESHING) {
                getPullToRefreshHeader().onPulling(handler);
            }
        }
    }

    public void stopRefresh() {
        if (status == Status.REFRESHING) {
            handler.showContent();
            status = Status.REFRESH_END;
            getPullToRefreshHeader().onAfterRefresh();
            status = Status.RESET;
            getPullToRefreshHeader().onReset();
        }
    }

    public void autoRefresh() {
        if (status == Status.RESET) {
            status = Status.REFRESH_BEGIN;
            getPullToRefreshHeader().onBeforeRefresh();
            if (onPullToRefreshCallback != null) {
                onPullToRefreshCallback.onRefreshBegin();
            }
            status = Status.REFRESHING;
            getPullToRefreshHeader().onRefreshing();
            handler.showHeader();
        }
    }

    @IntDef({Status.RESET, Status.PULLING, Status.REFRESH_BEGIN, Status.REFRESHING, Status.REFRESH_CANCELLED, Status.REFRESH_END})
    @Retention(RetentionPolicy.SOURCE)
    @interface Status {
        /**
         * 闲置状态
         */
        int RESET = 0;

        /**
         * 下拉中
         */
        int PULLING = 1;

        /**
         * 开始刷新
         */
        int REFRESH_BEGIN = 2;

        /**
         * 正在刷新
         */
        int REFRESHING = 3;

        /**
         * 刷新中断/取消
         */
        int REFRESH_CANCELLED = 4;

        /**
         * 刷新完毕/结束
         */
        int REFRESH_END = 5;
    }

    public interface OnPullToRefreshCallback {
        void onRefreshBegin();

        void onRefreshCancel();
    }
}
